package util.methods;

/***********************************************************
 * Typically, it should only be necessary to call the following for the full command string
 *      getBlastxCmd(String query, String database, String outfile, int nCPU)
 *      getBlastnCmd(String query, String database, String outfile, int nCPU)
 *      getBlastpCmd(String query, String database, String outfile, int nCPU)
 *      getTlastxCmd(String query, String database, String outfile, int nCPU)
 * as there are three methods to set the options if the user changes the defaults.
 * 
 * However, there are public methods to get the three parts;
 *      name of executable, fixed arguments, options
 * For formating, use:
 *      protein: getFormatp(String database)
 *      nucleotide: getFormatn(String database)
 *      
 * blastn: used by assembler, computing pairs, nucleotide against nucleotide database
 * tblastx: used for computing pairs (6 translation nt against nt)
 * blastx: used for nucleotide against protein database
 * blastp: used for protein sequence against protein database
 */
import java.io.File;
import java.util.Vector;
import jpave.dataholders.AAStatistics;
import jpave.manager.EditAnnoPanel;

public class BlastArgs {  
	public static final String [] validSearchPgms = {"blast", "diamond", "usearch"};
    // default args
    private static final String origBlastxOptions =  "-e1e-10 -b25 "; 
    private static final String origBlastpOptions =  "-e1e-10 -b25 ";
    private static final String origBlastnOptions =  "-e1e-40 -p70 -b25 ";
    private static final String origTblastxOptions = "-e1e-10 -b25 ";

    private static final String plusBlastxOptions =  "-evalue 1e-10 -max_target_seqs 25 ";
    private static final String plusBlastpOptions =  "-evalue 1e-10 -max_target_seqs 25 ";
    private static final String plusBlastnOptions =  "-evalue 1e-40 -max_target_seqs 25 ";
    private static final String plusTblastxOptions = "-evalue 1e-10 -max_target_seqs 25 ";
    // default diamond is --evalue 1e-03; needs this to be equivalent with blast 1e-10
    // don't compress as the programs don't
    private static final String diamondOptions = "--evalue 1e-03 --compress 0 --tmpdir tmpDmnd";
    private static final String usearchOptions = "-evalue 1e-10";
    private static final String usearchSuffix = ".udb";
    
    public static void availSearch() {
    		System.err.println("Available search programs: ");
    		if (isdiamond) System.err.println("  diamond - for blastx and blastp, annotation only");
        if (isUsearch) System.err.println("  usearch - for ORF blastx and blastp, annotation only");
        System.err.println("  blast - annotation, assembly, finding pairs, interactive blasting");
    }
    public static void availBlast() {
    		if (isblastplus) System.err.println("   Using blast+");
    		else System.err.println("   Using legacy blast");
    }
    /***********************************************
     * User defines the blast or diamond path in HOSTS.cfg
     * The new blast+ is the default if they do not define one.
     * It should find a blast path, and optionally a diamond path
     */
 
    public static void evalBlastPath(String bp, String dp, String up) {
     	boolean o=false, n=false, d=false, u=false;
     	if (!bp.trim().equals("")) 		o = oldBlast(bp);
     	if (o==false) 					n = newBlast(bp);
     	if (!dp.trim().equals(""))		d = diamond(dp);
     	if (!up.trim().equals(""))		u = usearch(up);
     	
     	if (!o && !n && !d && !u) {
     		if (bp.equals("") && dp.equals("") && up.equals("")) {
     			System.err.println("Warning: Cannot find a blast program to use");
     			System.err.println("   Try setting the blast path and optional diamond or usearch path in HOSTS.cfg");
     		}
            if (!bp.equals("")) System.err.println("Warning: Incorrect blast_path in HOSTS.cfg");
            if (!dp.equals("")) System.err.println("Warning: Incorrect diamond_path in HOSTS.cfg");
            if (!up.equals("")) System.err.println("Warning: Incorrect usearch_path in HOSTS.cfg");
         
            if (!UIHelpers.isApplet()) System.err.println("");
            return;
     	}
     	if (!n && !o) {
     		System.err.println("Warning: no blast found. Blast is needed for:");
     		System.err.println("1. If your going to be assembling sequences");
     		System.err.println("2. If your going to cluster sequences for multiTCW");
     		System.err.println("3. For interactive blasting");
     	}
     	// do not change constants diamond, usearch and blast as used elsewhere
     	if (o || u) searchPgms.add(EditAnnoPanel.SEARCH_PGM_DEFAULT);
     	if (isdiamond) searchPgms.add("diamond");
     	if (isUsearch) searchPgms.add("usearch");
     	searchPgms.add("blast");
    }
    private static boolean diamond(String p) {
		if (!p.endsWith("diamond")) {
			if (!pathExists("diamond_path", p, true)) return false;
			p = p + "/diamond";
		}
	 	if (!fileExists("diamond", p, true)) return false;
	 	
	 	diamondOp = diamondOptions;
	 	diamondPath = p;
	 	isdiamond = true;
	 	return true;
	}
    private static boolean usearch(String p) {
    		if (!p.endsWith("usearch")) {
			if (!pathExists("usearch_path", p, true)) return false;
			p = p + "/usearch";
		}
	 	if (!fileExists("usearch", p, true)) return false;
  	 	
  	 	usearchOp = usearchOptions;
  	 	usearchPath = p;
  	 	isUsearch = true;
  	 	return true;
  	}
    private static boolean oldBlast(String p) {
    		if (!pathExists("blast_path", p, true)) return false;
    		
    		p = p.trim() + "/"; 
		if (!fileExists("blast", p + "formatdb", false)) return false;
    
        isblast=true;
         formatp =  p + "formatdb -i ";
         formatn = p + "formatdb -p F -i ";
         format = p + "formatdb";
         blastxEx = p + "blastall -p blastx"; 
         blastpEx = p + "blastall -p blastp"; 
         blastnEx = p + "megablast"; 
         tblastxEx = p + "blastall -p tblastx";
         tblastnEx = "";
         blastxOp = origBlastxOptions; 
         blastpOp = origBlastpOptions; 
         blastnOp = origBlastnOptions; 
         tblastxOp = origTblastxOptions;
           
    		return true;
    }
    // if path is blank, then makeblastdb should be in the users path
    private static boolean newBlast(String p) {
    		if (!p.trim().equals("")) {
    			if (!pathExists("blast_path", p, false)) p = "";
    			else {
    				p = p.trim() + "/";
    				if (!fileExists("blast+", p + "makeblastdb", false)) p="";
    			}
    		}
    		isblastplus = true;
        formatp = p + "makeblastdb -dbtype prot -in ";
        formatn = p + "makeblastdb -dbtype nucl -in ";
        format = p + "makeblastdb";
        blastxEx = p + "blastx"; 
        blastpEx = p + "blastp"; 
        blastnEx = p + "blastn"; 
        tblastxEx = p + "tblastx";
        tblastnEx = p + "tblastn";
        blastxOp = plusBlastxOptions; 
        blastpOp = plusBlastpOptions; 
        blastnOp = plusBlastnOptions; 
        tblastxOp = plusTblastxOptions;    
    		return true;
    }
   
    private static boolean pathExists(String pgm, String path, boolean prt) {
	    	File f = new File(path);
	    	if (f.exists() && f.isDirectory()) return true;
	    
	    	String err;
		if (!f.isDirectory()) 		
    			err = "   The " + pgm + " " + path + " is not a valid directory";
		else 
   			err = "   The " + pgm + " "+ path + " does not exist";
		if (!UIHelpers.isApplet() && prt) System.err.println(err);
    	    return false;
    }
    private static boolean fileExists(String pgm, String file, boolean prt) {
	    	File f = new File(file);
	    	if (f.exists() && !f.isDirectory()) return true;
	    
	    	String err;
		if (f.isDirectory()) 		
				err = "   The " + pgm + " " + file + " is a directory";
		else 
				err = "   The " + pgm + " "+ file + " does not exist";
		if (!UIHelpers.isApplet() && prt) System.err.println(err);
		return false;
	}
    /*******************************************************
     * General queries
     */
    public static boolean isBlastPlus() {return isblastplus;}
    public static boolean isBlast() {return isblast;}
    public static boolean foundABlast() 
    {
	    	try
	    	{
	    		Process p = Runtime.getRuntime().exec(getBlastnExec());
	    		p.waitFor();
	    		return true;
	    	}
	    	catch(Exception e)
	    	{
	    		return false;
	    	}
    }

    /****************************************************************
     *  execAssm - methods for assembler
     * **************************************************************/
    public static String getFormatCmd() {
        return format;
    }
    // WMN Megablast translator added for doing PAVE assembly steps.
    // It's not fully complete because "-i", "-o" are not included, due to the way the assembler's blast threading is set up (they
    // are added later). Also it assumes all arguments are present and it adds "-FF" always. 
    public static String getAsmMegablastArgs(String database, String eval, float pctID, int score, String moreParams) {
	    	String cmd = "";
	    	if (isblastplus) {
	    		cmd = blastnEx +  " -db " +  database + " -evalue " + eval + " -dust no " + " -perc_identity " + pctID +
	    		" -min_raw_gapped_score " + score + " " + moreParams +
	    		" -outfmt 6 ";
	    	}
	    	else {
	    		cmd = blastnEx + " -e" + eval + " " + moreParams +	" -p" + pctID + " -s" + score  + " -D3 " + " -FF " + " -d "+ database ;   		
	    	}
	    	return cmd;
    }
    // Add in/out file to complete megablast command line (used in assembler)
    public static String getAsmInOutParams(String inFile, String outFile) {
	    	String ret = "";
	    	if (isblastplus) {
	    		ret = " -query " + inFile + " -out " + outFile;
	    	}
	    	else {
	    		ret = " -i " + inFile + " -o " + outFile;
	    	} 
	    	return ret;
    }
    // WN for some of the assembler exec calls, need to have a tokenized command line. This function
    // should be pretty general, in particular it handles quoted parameters correctly. 
    // Note, there must be a space between each flag and its argument, even if quoted, e.g. -X "Y Z".
    // (Actually this algorithm collapses multiple spaces within the quoted parameter, but it's good enough for blast)
    public static String[] cmdLineToArray(String in) {
	    	String[] array1 = in.trim().split("\\s+"); // step 1, split on spaces
	    	Vector<String> array2 = new Vector<String>(); // step2, put back together the ones starting/ending with "
	    	boolean inQuotedParam = false;
	    	for (String s : array1)
	    	{
	    		if (!inQuotedParam)
	    		{
	    			if (s.startsWith("\""))
	    			{
	    				inQuotedParam = true;
	    				s = s.replace("\"", "");
	    			}
	    			if (s.contains("\"") || s.contains("'"))
	    			{
	    				System.out.println("***WARNING Unable to process command line argument " + s + " from:\n" + in);
	    			}
	    			array2.add(s);
	    		}
	    		else
	    		{
	    			int curIdx = array2.size()-1;
	    			String curParam = array2.get(curIdx);
	    			if (s.endsWith("\""))
	    			{
	    				s = s.replace("\"", "");
	    				inQuotedParam = false;
	    			}
	    			if (s.contains("\"") || s.contains("'"))
	    			{
	    				System.out.println("***WARNING Unable to process command line argument " + s + " from:\n" + in);
	    			}
	    			curParam += " " + s;
	    			array2.set(curIdx, curParam);
	    		}
	    	}
	    	return array2.toArray(new String[0]);
    }
    
    /**********************************************************************
     *  execAnno - methods for annotater
     * ********************************************************************/
    public static boolean blastExists() {return (isblast || isblastplus || isdiamond);}
    /** diamond **/
    public static boolean isDiamond() {return isdiamond;}
    public static String getDiamondOptions() {return diamondOp; }
    public static String getDiamondOpDefaults() { return diamondOptions;}
    public static String getDiamondFormat(String database, String outfile) {
		return diamondPath + " makedb --in " + database + " --d " + outfile;
    }
    public static String getDiamondCmd(String query, String database, String outfile, 
    			String action, String blastOp, int nCPU) 
    {
    		String pgmPath = diamondPath + " blastx ";
		if (action.equals("blastp")) pgmPath = diamondPath + " blastp ";
		return pgmPath  + " --q " + query + " --d " + database + " --o " + outfile + 
			" " + blastOp + " --threads " + nCPU;
    }
    
    /** usearch **/
    public static boolean isUsearch() {return isUsearch;}
    public static String getUsearchOptions() {return usearchOp;}
    public static String getUsearchOpDefaults() {return usearchOptions;}
    public static String getUsearchFormat(String database, String outfile) {
    		outfile += usearchSuffix;
		return usearchPath + " -makeudb_ublast " + database + " -output " + outfile;
    }
    public static String getUsearchCmd(String query, String database, String outfile, String op) {
    		// requires an evalue
    		String uOp = (op.contains("evalue") && !op.contains("max_")) ? op : usearchOptions; 
    		return usearchPath + " -ublast " + query + " " + uOp + " -db " + database + " -blast6out " + outfile;
    }
 
    /** blast **/
    // Called from DoBlast for blastp, blastn, blastx
    public static String getBlastCmd(String query, String database, String outfile, String action, String blastOp, int nCPU) {
	 	if (isblastplus) return 
	 			action + " -query " + query + " -db " + database + " -out " + outfile + 
	            " -outfmt 6 " + blastOp + " -num_threads " + nCPU + " ";
	 	else return 
	 			"blastall -p " + action + " -i "  + query + " -d " + database + " -o " + outfile 
	            + " -m8 " + blastOp + " -a" + nCPU + " "; 
    }
    
    public static void setBlastxOptions(String s) {blastxOp = s;};
    public static void setBlastnOptions(String s) {blastnOp = s;};
    public static void setTblastxOptions(String s) {tblastxOp = s;};
    
    public static String getFormatp(String database) { return formatp + database;}   
    public static String getFormatn(String database) { return formatn + database;}
    // arguments the same for blastx, blastn and tblastx 
    public static String getBlastArgs(String query, String database, String outfile, int nCPU) {
        if (isblastplus) return 
            "-outfmt 6 -query " + query + " -db " + database + " -out " + outfile + " -num_threads " + nCPU + " ";
        else return
            "-m8 -i " + query + " -d " + database + " -o " + outfile + " -a" + nCPU + " ";
    }
    
    /** blastx nt to aa **/
    public static String getBlastxExec() {return blastxEx;}
    public static String getBlastxOptions() {return blastxOp;}
    public static String getBlastxOpDefaults() {
    	    if (isBlastPlus())return plusBlastxOptions; 
    	    else return origBlastxOptions;
    	}
    public static String getBlastxCmd(String query, String database, String outfile, int nCPU) {
        return getBlastxExec()  + " " + getBlastxOptions() + " " + getBlastArgs(query, database, outfile, nCPU);  
    }
    
    /** blastx aa to aa **/
    public static String getBlastpExec() {return blastpEx;}
    public static String getBlastpOptions() {return blastpOp;}
    public static String getBlastpCmd(String query, String database, String outfile, int nCPU) {
        return getBlastpExec()  + " " + getBlastpOptions() + " " + getBlastArgs(query, database, outfile, nCPU);  
    }
    
    /** blastn nt to nt **/
    public static String getBlastnExec() {return blastnEx;}
    public static String getBlastnOptions() {return blastnOp;}
    public static String getBlastnCmd(String query, String database, String outfile, int nCPU) {
        return getBlastnExec() + " " + getBlastnOptions() + " " + getBlastArgs(query, database, outfile, nCPU);  
    }
    
    /** blastn nt to nt with 6 frame translation**/
    public static String getTblastxExec() {return tblastxEx;}   
    public static String getTblastxOptions() {return tblastxOp;}
    public static String getTblastxCmd(String query, String database, String outfile, int nCPU) {
        return getTblastxExec() + " " + getBlastArgs(query, database, outfile, nCPU) + " " + getTblastxOptions();  
    }
   
    /*****************************************************************
     * sTCW interactive blast
     */
    public static String buildBlastCommand(String query, String db, String out, String type,
    		int cpu, String eval, boolean tabular, String moreParams)
    {
	    	String cmd = null;
	    	if (type.equals("blastn")) cmd = getBlastnExec();
	    	else if (type.equals("blastx")) cmd = getBlastxExec();
	    	else if (type.equals("blastp")) cmd = getBlastpExec();
	    	else if (type.equals("tblastn")) cmd = getTBlastnExec();
	    	else
	    	{
	    		System.err.println("invalid blast type:" + type);
	    		return "";
	    	}
	    	
	    	String args = " no args! ";
        if (isBlastPlus()) 
        {
        		args =  " -query " + query + " -db " + db + " -out " + out +  " ";
        }
        else
        {
        		args =  " -i " + query + " -d " + db + " -o " + out + " ";
        }
    	
	    	String fmt = " ";
	    	if (tabular)
	    	{
	    		fmt = (isBlastPlus() ? " -outfmt 6 " : " -m8 ");
	    	}
	    	String estr = " ";
	    	if (eval != null) estr = (isBlastPlus() ? " -evalue " + eval : " -e" + eval);
	    	String cpustr = " ";
	    	if (cpu > 1) cpustr = (isBlastPlus() ? " -a" + cpu : " -num_threads " + cpu);
	    	
	    	String ret = cmd + args + fmt + estr + cpustr + " " + moreParams;
	    	return ret;
    }
    // protein vs. nt, + only
    private static String getTBlastnExec() {return tblastnEx;}
    /*********************************************************************
     * mTCW no CPU, no diamond
     */
    public static String getBlastArgs(String query, String database, String outfile) {
        if (isblastplus) return 
            "-outfmt 6 -query " + query + " -db " + database + " -out " + outfile +  " ";
        else return
            "-m8 -i " + query + " -d " + database + " -o " + outfile + " ";
    }   
    public static String getBlastArgs(String query, String database, String outfile, boolean tabular) {
        if (isblastplus)
        {
	        	if (tabular) return 
	            	"-outfmt 6 -query " + query + " -db " + database + " -out " + outfile +  " ";
	        	else return 
	        		" -query " + query + " -db " + database + " -out " + outfile +  " ";
        } 
        else 
        {
	        	if (tabular) return
	            	"-m8 -i " + query + " -d " + database + " -o " + outfile + " ";
	        	else return " -i " + query + " -d " + database + " -o " + outfile + " ";
        }
    }  
    public static String getBlastpCmd(String query, String database, String outfile, boolean tabular) {
        return getBlastpExec()  + " " + getBlastpOptions() + " " + getBlastArgs(query, database, outfile, tabular);  
    }  
    public static boolean isNucleotide(String line) {
    		int nt=0, bad=0, len = line.length(); 
		for (int i = 0; i<len; i++) {
			char c = line.charAt(i);
			if (AAStatistics.isDNALetter(c)) nt++;
			// else if (AAStatistics.isDNAAmbiguity(c)) nt++; // CAS 4July15 makes proteins look like nucleotides
			else if (c=='.' || c=='*' || c=='-' || c==' ') nt++;
			else bad++;
		}
		if (nt==len) return true;
		else if (bad<= 5&& len>30) return true;
		else return false;
    }
    public static boolean isProtein(String line) {
    		int bad=0, pr=0, len=line.length(); 
		for (int i = 0; i<len; i++) {
			char c = line.charAt(i);
			if (AAStatistics.isAcidLetter(c)) pr++;
			else if (c=='.' || c=='*' || c=='-' || c==' ') pr++;
			else bad++;
		}
		if (pr==len) return true;
		else if (bad<=5 && len>30) return true;
		else return false;
    }
    public static Vector <String> getSearchPgms() {return searchPgms;}
    public static String [] getValidSearchPgms() {return validSearchPgms;}
    public static Vector <String> getValidSearchPgmVec() {
    		Vector <String> arr = new Vector <String>();
    		for (int i=0; i<validSearchPgms.length; i++) arr.add(validSearchPgms[i]);
    		return arr;
    }
    private static boolean isUsearch=false;
    private static String usearchPath="", usearchOp="";
    private static boolean isblast=false;
    private static boolean isblastplus=false;
    private static boolean isdiamond=false;
    private static String diamondPath="", diamondOp;
    private static String formatp, formatn, format;
    private static String blastxEx, blastpEx, blastnEx, tblastxEx, tblastnEx;
    private static String blastxOp, blastpOp, blastnOp, tblastxOp;
    private static Vector <String> searchPgms = new Vector <String> ();
}
